#include "plotter.h"
#include <math.h>
#include <QDebug>

//--------------------------------------------------------------------------
// Local defines
//--------------------------------------------------------------------------
#define MAX_SCREENSIZE 2048

//--------------------------------------------------------------------------
// CPlotter constructor and destructor
//--------------------------------------------------------------------------
CPlotter::CPlotter(QWidget *parent) :
  QFrame(parent)
{
  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  setFocusPolicy(Qt::StrongFocus);
  setAttribute(Qt::WA_PaintOnScreen,false);
  setAutoFillBackground(false);
  setAttribute(Qt::WA_OpaquePaintEvent, false);
  setAttribute(Qt::WA_NoSystemBackground, true);

  //default waterfall color scheme
  for( int i=0; i<256; i++) {
    if( (i<43) )
      m_ColorTbl[i].setRgb( 0,0, 255*(i)/43);
    if( (i>=43) && (i<87) )
      m_ColorTbl[i].setRgb( 0, 255*(i-43)/43, 255 );
    if( (i>=87) && (i<120) )
      m_ColorTbl[i].setRgb( 0,255, 255-(255*(i-87)/32));
    if( (i>=120) && (i<154) )
      m_ColorTbl[i].setRgb( (255*(i-120)/33), 255, 0);
    if( (i>=154) && (i<217) )
      m_ColorTbl[i].setRgb( 255, 255 - (255*(i-154)/62), 0);
    if( (i>=217)  )
      m_ColorTbl[i].setRgb( 255, 0, 128*(i-217)/38);
  }

  m_StartFreq = 0;
  m_Span = 60.0;                 //Units: kHz
  m_hdivs = HORZ_DIVS;
  m_MaxdB = 50;
  m_MindB = -10;
  m_dBStepSize = 10;
  m_FreqUnits = 1;
  m_Running = false;
  m_2DPixmap = QPixmap(0,0);
  m_OverlayPixmap = QPixmap(0,0);
  m_WaterfallPixmap = QPixmap(0,0);
  m_ScalePixmap = QPixmap(0,0);
  m_Size = QSize(0,0);
  m_Percent2DScreen = 40;   //percent of screen used for 2D spectrum display
  m_fQSO = 125;
}

CPlotter::~CPlotter()
{
}

//--------------------------------------------------------------------------
// Sizing interface for CPlotter
//--------------------------------------------------------------------------
QSize CPlotter::minimumSizeHint() const
{
  return QSize(50, 50);
}

QSize CPlotter::sizeHint() const
{
  return QSize(180, 180);
}

//--------------------------------------------------------------------------
// Called when screen size changes so must recalculate bitmaps
//--------------------------------------------------------------------------
void CPlotter::resizeEvent(QResizeEvent* )
{
  if(!size().isValid()) return;
  if( m_Size != size() ) {
    //if changed, resize pixmaps to new screensize
    m_Size = size();
    int w = m_Size.width();
    int h = m_Size.height() - 30;
    if(m_wide) {
      m_WaterfallPixmap = QPixmap(w,(100-m_Percent2DScreen)*h/100);
    } else {
      m_WaterfallPixmap = QPixmap(w,h);
    }
    m_WaterfallPixmap.fill(Qt::black);
    m_ScalePixmap = QPixmap(w,30);
    m_ScalePixmap.fill(Qt::white);
    m_OverlayPixmap = QPixmap(w,m_Percent2DScreen*h/100);
    m_OverlayPixmap.fill(Qt::black);
    m_2DPixmap = QPixmap(w,m_Percent2DScreen*h/100);
    m_2DPixmap.fill(Qt::black);
  }
  DrawOverlay();
}

//--------------------------------------------------------------------------
// Called by QT when screen needs to be redrawn
//--------------------------------------------------------------------------
void CPlotter::paintEvent(QPaintEvent *)
{
  QPainter painter(this);
  int y0 = 30;
  int h = m_Size.height() - y0;
  painter.drawPixmap(0,0,m_ScalePixmap);
  painter.drawPixmap(0,y0,m_WaterfallPixmap);
  if(m_wide)  {
    painter.drawPixmap(0,y0+(100-m_Percent2DScreen)*h/100,m_2DPixmap);
  }

  //tell interface that its ok to signal a new line of fft data
  //###  m_pSdrInterface->ScreenUpdateDone();
}

//--------------------------------------------------------------------------
// Called to update spectrum data for displaying on the screen
//--------------------------------------------------------------------------
void CPlotter::draw(float s[])
{
  int i,w,h;
  qint32 n60;
  qint32 fftbuf[MAX_SCREENSIZE];
  QPoint LineBuf[MAX_SCREENSIZE];

  // draw the waterfall
  w = m_WaterfallPixmap.width();
  h = m_WaterfallPixmap.height();
  double gain = pow(10.0,0.025*(m_plotGain-10));

  //move current data down one line
  //(must do this before attaching a QPainter object)
  m_WaterfallPixmap.scroll(0,1,0,0, w, h);
  QPainter painter1(&m_WaterfallPixmap);

// Time according to this computer
  qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
  n60 = (ms/1000) % 60;

  //draw new line of fft data at top of waterfall bitmap
  for(i=0; i<w; i++) {
    float y = 10.0*log10(s[i]);
    if(n60<m_n60z) {
      fftbuf[i] = 255;
    } else {
      fftbuf[i] = 5.0*gain*(y + m_plotZero - 90.0);
    }
    if (fftbuf[i]<0) fftbuf[i]=0;
    if (fftbuf[i]>255) fftbuf[i]=255;
    painter1.setPen(m_ColorTbl[ fftbuf[i] ]);
    painter1.drawPoint(i,0);
  }
  m_n60z=n60;

  if(m_wide) {
    // draw the 2D spectrum
    w = m_2DPixmap.width();
    h = m_2DPixmap.height();
    //first copy into 2Dbitmap the overlay bitmap.
    m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h);

    QPainter painter2(&m_2DPixmap);
    //draw the 2D spectrum

    for(i=0; i<w; i++) {
      float y = 10.0*log10(s[i]);
      fftbuf[i] = 2.0*(y - 103) + 0.5*h + m_plotZero;
      if (fftbuf[i]<0) fftbuf[i]=0;
      if (fftbuf[i]>h) fftbuf[i]=h;
    }
    painter2.setPen( Qt::green );
    for(i=0; i<w; i++) {
      LineBuf[i].setX(i);
      LineBuf[i].setY(h - fftbuf[i]);
    }
    //  painter2.drawPoints(LineBuf,w);
    painter2.drawPolyline(LineBuf,w);
  }
  //trigger a new paintEvent
  update();
}

//--------------------------------------------------------------------------
// The overlay does not need to be recreated every fft data update.
//--------------------------------------------------------------------------
void CPlotter::DrawOverlay()
{
  if(m_OverlayPixmap.isNull()) return;
  int w = m_OverlayPixmap.width();
  int h = m_OverlayPixmap.height();
  int x,y;
  float pixperdiv;
  QRect rect;
  QRect rect0;

  QPainter painter(&m_OverlayPixmap);
  painter.initFrom(this);

  QPainter painter0(&m_ScalePixmap);
  painter0.initFrom(this);

  double FreqPerDiv=5.0;
  if(!m_wide) FreqPerDiv=0.2;

  double df = m_binsPerPixel*96.0/32768.0;
// Should not allow w*df > 96.0 !!!
  m_hdivs = w*df/FreqPerDiv + 0.9999;
  m_Span = w*df;
//  qDebug() << "A" << m_wide << m_Span;

  //fill background with gradient
  QLinearGradient gradient(0, 0, 0 ,h);
  gradient.setColorAt(1, Qt::black);
  //	gradient.setColorAt(0, Qt::gray);
  gradient.setColorAt(0, Qt::darkBlue);
  painter.setBrush(gradient);
  painter.drawRect(0, 0, w, h);

  m_ScalePixmap.fill(Qt::white);
  painter0.drawRect(0, 0, w, 30);

  //create Font to use for scales
  QFont Font("Arial");
  Font.setPointSize(12);
  QFontMetrics metrics(Font);
  y = h/VERT_DIVS;
  if(!m_wide) y=12;                        //### Kludge? ###
  if(y<metrics.height())
    Font.setPixelSize(y);
  Font.setWeight(QFont::Normal);
  painter.setFont(Font);
  painter0.setFont(Font);
  painter0.setPen(Qt::black);

  //draw vertical grids
  pixperdiv = FreqPerDiv/df;
  y = h - h/VERT_DIVS;
  for( int i=1; i<m_hdivs; i++) {
    x = (int)( (float)i*pixperdiv );
    painter.setPen(QPen(Qt::white, 1,Qt::DotLine));
    painter.drawLine(x, 0, x , y);
    painter.drawLine(x, h-5, x , h);
    painter0.drawLine(x,20,x,30);
  }

  if(m_wide) {
    for( int i=1; i<5*m_hdivs; i++) {
      x = i*pixperdiv/5.0;
      painter0.drawLine(x,25,x,30);
    }
  } else {
    for( int i=1; i<10*m_hdivs; i++) {
      x = i*pixperdiv/10;
      y=25;
      if ((i%10) == 5) y=20;
      painter0.drawLine(x,y,x,30);
    }
  }

  //draw frequency values
  MakeFrequencyStrs();
  painter.setPen(Qt::cyan);
  y = h - (h/VERT_DIVS);
  for( int i=0; i<=m_hdivs; i++) {
    if(0==i) {
      //left justify the leftmost text
      x = (int)( (float)i*pixperdiv);
      rect.setRect(x ,y, (int)pixperdiv, h/VERT_DIVS);
      painter.drawText(rect, Qt::AlignLeft|Qt::AlignVCenter,
                       m_HDivText[i]);
      rect0.setRect(x,0, (int)pixperdiv, 20);
      painter0.drawText(rect0, Qt::AlignLeft|Qt::AlignVCenter,
                       m_HDivText[i]);
    }
    else if(m_hdivs == i) {
      //right justify the rightmost text
      x = (int)( (float)i*pixperdiv - pixperdiv);
      rect.setRect(x ,y, (int)pixperdiv, h/VERT_DIVS);
      painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter,
                       m_HDivText[i]);
      rect0.setRect(x,0, (int)pixperdiv, 20);
      painter0.drawText(rect0, Qt::AlignRight|Qt::AlignVCenter,
                       m_HDivText[i]);
    } else {
      //center justify the rest of the text
      x = (int)( (float)i*pixperdiv - pixperdiv/2);
      rect.setRect(x ,y, (int)pixperdiv, h/VERT_DIVS);
      painter.drawText(rect, Qt::AlignHCenter|Qt::AlignVCenter,
                       m_HDivText[i]);
      rect0.setRect(x,0, (int)pixperdiv, 20);
      painter0.drawText(rect0, Qt::AlignHCenter|Qt::AlignVCenter,
                       m_HDivText[i]);
    }
  }

  if(m_wide) {
    x = XfromFreq(float(m_fQSO));
    QPen pen0(Qt::green, 3);
    painter0.setPen(pen0);
    painter0.drawLine(x,15,x,30);
  }

  //draw horizontal grids
  pixperdiv = (float)h / (float)VERT_DIVS;
  painter.setPen(QPen(Qt::white, 1,Qt::DotLine));
  for( int i=1; i<VERT_DIVS; i++) {
    y = (int)( (float)i*pixperdiv );
    painter.drawLine(0, y, w, y);
  }

  //draw amplitude values
  painter.setPen(Qt::yellow);
  Font.setWeight(QFont::Light);
  painter.setFont(Font);
  int dB = m_MaxdB;
  for( int i=0; i<VERT_DIVS-1; i++) {
    y = (int)( (float)i*pixperdiv );
    painter.drawStaticText(5, y-1, QString::number(dB)+" dB");
    dB -= m_dBStepSize;
  }
  m_MindB = m_MaxdB - (VERT_DIVS)*m_dBStepSize;

  if(!m_Running) {
    //if not running so is no data updates to draw to screen
    //copy into 2Dbitmap the overlay bitmap.
    m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h);
    //trigger a new paintEvent
    update();
  }
}

//--------------------------------------------------------------------------
// Helper function Called to create all the frequency division text
//strings based on start frequency, span frequency, frequency units.
//Places in QString array m_HDivText
//Keeps all strings the same fractional length
//--------------------------------------------------------------------------
void CPlotter::MakeFrequencyStrs()
{
  float StartFreq = m_StartFreq;
  float freq;
  int i,j;
  int FreqPerDiv=5;

  // ### This is not right, yet ###
  if(!m_wide) {
    m_FreqUnits = 1;
    FreqPerDiv = 200;
    StartFreq = -1000.0;
  }
  int numfractdigits = (int)log10((double)m_FreqUnits);

  if(1 == m_FreqUnits) {
    //if units is Hz then just output integer freq
    for(int i=0; i<=m_hdivs; i++) {
      freq = StartFreq/(float)m_FreqUnits;
      m_HDivText[i].setNum((int)freq);
      StartFreq += FreqPerDiv;
    }
    return;
  }
  //here if is fractional frequency values
  //so create max sized text based on frequency units
  for(int i=0; i<=m_hdivs; i++) {
    freq = StartFreq/(float)m_FreqUnits;
    m_HDivText[i].setNum(freq,'f', numfractdigits);
    StartFreq += FreqPerDiv;
  }
  //now find the division text with the longest non-zero digit
  //to the right of the decimal point.
  int max = 0;
  for(i=0; i<=m_hdivs; i++) {
    int dp = m_HDivText[i].indexOf('.');
    int l = m_HDivText[i].length()-1;
    for(j=l; j>dp; j--) {
      if(m_HDivText[i][j] != '0')
        break;
    }
    if( (j-dp) > max)
      max = j-dp;
  }
  //truncate all strings to maximum fractional length
  StartFreq = m_CenterFreq - 0.5*m_Span;
  for( i=0; i<=m_hdivs; i++) {
    freq = (float)StartFreq/(float)m_FreqUnits;
    m_HDivText[i].setNum(freq,'f', max);
    StartFreq += FreqPerDiv;
  }
}

//--------------------------------------------------------------------------
// Helper functions to convert to/from screen coordinates to frequency
//--------------------------------------------------------------------------
int CPlotter::XfromFreq(float f)
{
  float w = m_OverlayPixmap.width();
  float StartFreq = m_CenterFreq - 0.5*m_Span;
  int x = (int) w * (f - StartFreq)/m_Span;
  if(x<0 )
    return 0;
  if(x>(int)w)
    return m_OverlayPixmap.width();
  return x;
}

float CPlotter::FreqfromX(int x)
{
  float w = m_OverlayPixmap.width();
  float f =m_CenterFreq - 0.5*m_Span + m_Span * x/w;
  return f;
}

void CPlotter::SetRunningState(bool running)
{
  m_Running = running;
}

void CPlotter::setPlotZero(int plotZero)
{
  m_plotZero=plotZero;
}

int CPlotter::getPlotZero()
{
  return m_plotZero;
}

void CPlotter::setPlotGain(int plotGain)
{
  m_plotGain=plotGain;
}

int CPlotter::getPlotGain()
{
  return m_plotGain;
}

void CPlotter::SetPercent2DScreen(int percent)
{
  m_Percent2DScreen=percent;
  m_Size = QSize(0,0);
  resizeEvent(NULL);
}

void CPlotter::SetCenterFreq(quint64 f)
{
// f is the integer kHz portion of cfreq, from Linrad packets
  int ns = (f+m_FreqOffset-0.5*m_Span)/5.0 + 0.5;
  double fs = 5*ns;
  m_CenterFreq = fs + 0.5*m_Span;
}

qint64 CPlotter::centerFreq()
{
  return m_CenterFreq;
}

void CPlotter::SetStartFreq(quint64 f)
{
  m_StartFreq=f;
//    resizeEvent(NULL);
  DrawOverlay();
}

qint64 CPlotter::startFreq()
{
  return m_StartFreq;
}

void CPlotter::SetFreqOffset(quint64 f)
{
  m_FreqOffset=f;
//    resizeEvent(NULL);
  DrawOverlay();
}

qint64 CPlotter::freqOffset()
{
  return m_FreqOffset;
}

int CPlotter::plotWidth()
{
  return m_WaterfallPixmap.width();
}

void CPlotter::SetMaxdB(int max)
{
  m_MaxdB=max;
}

void CPlotter::SetdBStepSize(int stepsz)
{
  m_dBStepSize=stepsz;
}

void CPlotter::UpdateOverlay()
{
  DrawOverlay();
}

void CPlotter::setWide(bool b)
{
  m_wide=b;
}

void CPlotter::setBinsPerPixel(int n)
{
  m_binsPerPixel = n;
}

int CPlotter::binsPerPixel()
{
  return m_binsPerPixel;
}

void CPlotter::setFQSO(int x, bool bf)
{
  if(bf) {
    // x is freq in kHz
    m_fQSO=x;
  } else {
    // x is pixel number
    x=x-10;
    if(x<0) x=0;
    if(x>m_Size.width()) x=m_Size.width();
    m_fQSO = int(FreqfromX(x)+0.5);
    gcom0_.qsoFreq = m_fQSO;
  }
  DrawOverlay();
}

int CPlotter::fQSO()
{
  return m_fQSO;
}
